package io.gitee.zhucan123.core.infrastructure.dataconversion;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.WritableTypeId;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;

/**
 * 县域Jackson json 序列化，将 java.time.LocalDateTime 序列化成时间戳
 * @author guodu
 * @date 2020/2/24
 */
public class JacksonLocalDateTimeSerializer extends JSR310FormattedSerializerBase<LocalDateTime>
    implements ContextualSerializer {

  private static final long serialVersionUID = 1L;
  private final ZoneId zoneId;

  public JacksonLocalDateTimeSerializer(ZoneId zoneId, DateTimeFormatter f) {
    super(LocalDateTime.class, f);
    this.zoneId = zoneId;
  }

  private JacksonLocalDateTimeSerializer(JacksonLocalDateTimeSerializer base, Boolean useTimestamp, Boolean useNanoseconds, DateTimeFormatter f) {
    super(base, useTimestamp, useNanoseconds, f, null);
    this.zoneId = base.getZoneId();
  }

  protected DateTimeFormatter _defaultFormatter() {
    return DateTimeFormatter.ISO_LOCAL_DATE_TIME;
  }

  @Override
  public void serialize(LocalDateTime value, JsonGenerator g, SerializerProvider provider)
      throws IOException
  {
    if (useTimestamp(provider)) {
      Instant instant = value.atZone(this.zoneId).toInstant();
      g.writeNumber(instant.toEpochMilli());
    } else {
      DateTimeFormatter dtf = _formatter;
      if (dtf == null) {
        dtf = _defaultFormatter();
      }
      g.writeString(value.format(dtf));
    }
  }

  @Override
  public void serializeWithType(LocalDateTime value, JsonGenerator g, SerializerProvider provider,
                                TypeSerializer typeSer) throws IOException
  {
    WritableTypeId typeIdDef = typeSer.writeTypePrefix(g,
        typeSer.typeId(value, serializationShape(provider)));
    // need to write out to avoid double-writing array markers
    if (typeIdDef.valueShape == JsonToken.START_ARRAY) {
      _serializeAsArrayContents(value, g, provider);
    } else {
      DateTimeFormatter dtf = _formatter;
      if (dtf == null) {
        dtf = _defaultFormatter();
      }
      g.writeString(value.format(dtf));
    }
    typeSer.writeTypeSuffix(g, typeIdDef);
  }

  private final void _serializeAsArrayContents(LocalDateTime value, JsonGenerator g,
                                               SerializerProvider provider) throws IOException
  {
    g.writeNumber(value.getYear());
    g.writeNumber(value.getMonthValue());
    g.writeNumber(value.getDayOfMonth());
    g.writeNumber(value.getHour());
    g.writeNumber(value.getMinute());
    final int secs = value.getSecond();
    final int nanos = value.getNano();
    if ((secs > 0) || (nanos > 0)) {
      g.writeNumber(secs);
      if (nanos > 0) {
        if (useNanoseconds(provider)) {
          g.writeNumber(nanos);
        } else {
          g.writeNumber(value.get(ChronoField.MILLI_OF_SECOND));
        }
      }
    }
  }

  @Override // since 2.9
  protected JsonToken serializationShape(SerializerProvider provider) {
    return useTimestamp(provider) ? JsonToken.START_ARRAY : JsonToken.VALUE_STRING;
  }

  @Override
  protected JSR310FormattedSerializerBase<LocalDateTime> withFormat(Boolean useTimestamp, DateTimeFormatter f, JsonFormat.Shape shape) {
    return new JacksonLocalDateTimeSerializer(this, useTimestamp, _useNanoseconds, f);
  }

  @Override
  protected JSR310FormattedSerializerBase<?> withFeatures(Boolean writeZoneId, Boolean writeNanoseconds) {
    return new JacksonLocalDateTimeSerializer(this, _useTimestamp, writeNanoseconds, _formatter);
  }

  public ZoneId getZoneId() {
    return zoneId;
  }
}
